<?php

class Nuo
{

    /**
     * @var
     */
    static protected $_vars = array();

    /**
     * define hook
     * @var array
     */
    static protected $_listeners = array();

    /**
     * @var array
     */
    static protected $_apis = array();

    /**
     * @var array
     */
    static protected $_helpers = array();

    /**
     * @var array
     */
    static protected $_tables = array();

    /**
     * @var array
     */
    static protected $_kinds = array();

    /**
     * @var array
     */
    static protected $_modules = array();

    /**
     * @var int
     */
    static protected $_siteId = 1;

    /**
     * define revision
     * @var int
     */
    static protected $_revision = 100;

    /**
     * @var string
     */
    static protected $_baseUrl = '/';

    /**
     * @var string
     */
    static protected $_baseHref = '/';

    /**
     * @var string
     */
    static protected $_tablePrefix = 'nuo_';

    /**
     * alias of resource.
     * @var array
     */
    static protected $_alias = array();

    /**
     * check boot.
     */
    static protected $_bootstrap = null;

    /**
     * @var user
     */
    static protected $_viewer = null;

    /**
     * @var string
     */
    static protected $_errorCode = null;

    /**
     * error mode.
     * @var int
     */
    static protected $_errorReporting = null;

    /**
     * @var Request
     */
    static protected $_request = null;

    /**
     * get response
     */
    static protected $_response = null;
    
    /**
     * @var router
     */
    static protected $_router = null;

    /**
     * initialize
     * @return void
     */
    static public function bootstrap()
    {
        self::registerErrorLog();
        require_once NUO_APP_PATH . '/module/core/bootstrap.php';
        self::$_bootstrap = new Core_Bootstrap;

    }

    /**
     * Register error handlers
     */
    static public function registerErrorLog()
    {
        // Compat
        defined('E_RECOVERABLE_ERROR') || define('E_RECOVERABLE_ERROR', 4096);
        defined('E_DEPRECATED') || define('E_DEPRECATED', 8192);
        defined('E_USER_DEPRECATED') || define('E_USER_DEPRECATED', 16384);

        // Set mask
        self::$_errorReporting = E_ERROR | E_COMPILE_ERROR | E_COMPILE_WARNING | E_CORE_ERROR | E_CORE_WARNING | E_ERROR | E_PARSE | E_RECOVERABLE_ERROR | E_USER_ERROR;

        // Register
        set_error_handler(array(
            'Nuo',
            'handleError'
        ));
        set_exception_handler(array(
            'Nuo',
            'handleException'
        ));
    }

    /**
     * Error handler
     *
     * @param integer $errno
     * @param string $errstr
     * @param string $errfile
     * @param integer $errline
     * @param array $errcontext
     */
    static public function handleError($errno, $errstr, $errfile = null, $errline = null, array $errcontext = null)
    {
        $log = Nuo::getHelper('core@log') -> getLog('error');

        // Force fatal errors to get reported
        $reporting = error_reporting() | self::$_errorReporting;

        $fatal = false;

        // Log
        if ($reporting & $errno)
        {
            switch( $errno )
            {
                case E_USER_NOTICE :
                case E_NOTICE :
                    $level = Zend_Log::NOTICE;
                    break;
                default :
                case E_USER_WARNING :
                case E_WARNING :
                case E_COMPILE_WARNING :
                case E_CORE_WARNING :
                case E_DEPRECATED :
                case E_STRICT :
                case E_USER_DEPRECATED :
                    $level = Zend_Log::WARN;
                    break;
                case E_COMPILE_ERROR :
                case E_CORE_ERROR :
                case E_ERROR :
                case E_PARSE :
                case E_RECOVERABLE_ERROR :
                case E_USER_ERROR :
                    $level = Zend_Log::ERR;
                    $fatal = true;
                    break;
            }
            //$backtrace = debug_backtrace();
            //ob_start();
            //debug_print_backtrace();
            //$backtrace = ob_get_clean();
            self::getErrorCode(true);
            $backtrace = self::formatBacktrace(array_slice(debug_backtrace(), 1));
            $log -> log(sprintf('[%1$d] %2$s (%3$s) [%4$d]' . PHP_EOL . '%5$s', $errno, $errstr, $errfile, $errline, $backtrace), $level);
        }

        // Handle fatal with nice response for user
        if ($fatal)
        {
            // self::_sendFatalResponse();
        }

        return !defined('_ENGINE_ERROR_SILENCE') || _ENGINE_ERROR_SILENCE;
    }

    /**
     * Exception handler
     *
     * @param Exception $exception
     */
    static public function handleException($exception)
    {
        // If no log, just do normally?
        $log = Nuo::getHelper('core@log') -> getLog('error');

        // Ignore Engine_Exception since it has built-in logging
        $output = 'Error Code: ' . self::getErrorCode(true) . PHP_EOL . $exception -> __toString();
        $log -> log($output, Zend_Log::ERR);

        return !defined('_ENGINE_ERROR_SILENCE') || _ENGINE_ERROR_SILENCE;
    }

    static public function formatBacktrace($backtrace)
    {
        $output = '';
        $output .= 'Error Code: ' . self::getErrorCode() . PHP_EOL;
        $output .= 'Stack trace:' . PHP_EOL;
        $index = 0;
        $index = 0;
        foreach ($backtrace as $index => $stack)
        {
            // Process args
            $args = array();
            if (!empty($stack['args']))
            {
                foreach ($stack['args'] as $argIndex => $argValue)
                {
                    if (is_object($argValue))
                    {
                        $args[$argIndex] = get_class($argValue);
                    }
                    else
                    if (is_array($argValue))
                    {
                        $args[$argIndex] = 'Array';
                        //substr(print_r($argValue, true), 0, 32);
                    }
                    else
                    if (is_string($argValue))
                    {
                        $args[$argIndex] = "'" . substr($argValue, 0, 32) . (strlen($argValue) > 32 ? '...' : '') . "'";
                    }
                    else
                    {
                        $args[$argIndex] = print_r($argValue, true);
                    }
                }
            }
            // Process message
            $output .= sprintf('#%1$d %2$s(%3$d): %4$s%5$s%6$s(%7$s)', $index, (!empty($stack['file']) ? $stack['file'] : '(unknown file)'), (!empty($stack['line']) ? $stack['line'] : '(unknown line)'), (!empty($stack['class']) ? $stack['class'] : ''), (!empty($stack['type']) ? $stack['type'] : ''), $stack['function'], join(', ', $args));
            $output .= PHP_EOL;
        }

        // Throw main in there for the hell of it
        $output .= sprintf('#%1$d {main}', $index + 1);

        return $output . PHP_EOL;
    }

    static protected function _sendFatalResponse()
    {
        // Clean any previous output from buffer
        while (ob_get_level() > 0)
        {
            ob_end_clean();
        }

        // Make body
        $body = file_get_contents(APPLICATION_PATH . '/application/offline.html');
        $body = str_replace('%__ERROR_CODE__%', 'Error code: ' . self::getErrorCode(), $body);

        // Send firephp headers
        if (APPLICATION_ENV == 'development')
        {
            try
            {
                $request = new Zend_Controller_Request_Http();
                $response = new Zend_Controller_Response_Http();

                $headers = Zend_Wildfire_Channel_HttpHeaders::getInstance();
                $headers -> setResponse($response);
                $headers -> setRequest($request);
                $headers -> flush();

                $response -> setBody($body);
                $response -> sendHeaders();
                $response -> sendResponse();

                die(1);
            }
            catch( Exception $e )
            {
                // Will fall through to stuff below
            }
        }

        // Send normal error message
        echo $body;
        die(1);
    }

    /**
     * Creates a random error code
     *
     * @param bool $reset
     */
    static public function getErrorCode($reset = false)
    {
        if ($reset === true || self::$_errorCode == null)
        {
            $code = md5(uniqid('', true));
            self::$_errorCode = substr($code, 0, 2) . substr($code, 15, 2) . substr($code, 30, 2);
        }
        return self::$_errorCode;
    }

    /**
     * lazy load registry
     * @param string $name
     * @param any $default
     * @return void
     */
    static public function get($name, $default = null)
    {
        if (!isset(self::$_vars[$name]))
        {
            if (method_exists(self::$_bootstrap, $method = 'init' . $name))
            {
                return self::$_vars[$name] = self::$_bootstrap -> {$method}();
            }
            return $default;
        }
        return self::$_vars[$name];
    }

    /**
     * set values
     * @param string $name
     * @param any $value
     */
    static public function set($name, $value)
    {
        self::$_vars[$name] = $value;
    }

    /**
     * remove object from lazy load to save memory usage if needed.
     * @param string $name
     * @return void
     */
    static public function remove($name)
    {
        if (isset(self::$_vars[$name]))
        {
            unset(self::$_vars[$name]);
        }
    }

    /**
     * return bootstrap.
     */
    static public function getBootstrap()
    {
        return $this -> _bootstrap;
    }

    /**
     * @var array $alias
     * @return void
     */
    static public function setAlias($alias)
    {
        self::$_alias = $alias;
    }

    /**
     * add alias
     * @param string $name
     * @param string $value
     * @return void
     */
    static public function addAlias($name, $value)
    {
        self::$_alias[$name] = $value;
    }

    /**
     * remove alias from register
     * @param string $name
     * @return void
     */
    static public function removeAlias($name)
    {
        if (isset(self::$_alias[$name]))
        {
            unset(self::$_alias[$name]);
        }
    }

    /**
     * @param string $revision
     * @return void
     */
    static public function setRevision($revision)
    {
        self::$_revision = (string)$revision;
    }

    /**
     * revision
     * @return int/string
     */
    static public function getRevision()
    {
        return self::$_revision;
    }

    /**
     * @return string
     */
    static public function getTablePrefix()
    {
        return self::$_tablePrefix;
    }

    /**
     * set table prefix
     * @param string $tablePrefix
     * @return void
     */
    static public function setTablePrefix($tablePrefix)
    {
        self::$_tablePrefix = (string)$tablePrefix;
    }

    /**
     * quote get table name
     * @param string $name
     * @return string
     */
    static public function getTableName($name)
    {
        return self::$_tablePrefix . $name;
    }

    /**
     * @return string
     */
    static public function getBaseHref()
    {
        return self::$_baseHref;
    }

    /**
     * get base url, etc: /nuo2/
     * always end by '/'
     * @return string
     */
    static public function getBaseUrl()
    {
        return self::$_baseUrl;
    }

    /**
     * @param string $href
     * @return void
     */
    static public function setBaseHref($href)
    {
        self::$_baseHref = trim($href, '/') . '/';
    }

    /**
     * @param string $baseUrl
     * @return void
     */
    static public function setBaseUrl($baseUrl)
    {
        self::$_baseUrl = $baseUrl;
    }

    /**
     * @return int
     */
    static public function getSiteId()
    {
        return self::$_siteId;
    }

    /**
     * @param int $siteId
     * @return void
     */
    static public function setSiteId($siteId)
    {
        self::$_siteId = $siteId;
    }

    /**
     * @param array $kinds
     * @return void
     */
    static public function setKinds($kinds)
    {
        self::$_kinds = $kinds;
    }

    /**
     * get all kinds specifics
     * @return array
     */
    static public function getKinds()
    {
        return self::$_kinds;
    }

    /**
     * get api object
     * @param string $name, etc: core@testcollate
     * @return api
     */
    static public function getHelper($name)
    {
        return isset(self::$_helpers[$name]) ? self::$_helpers[$name] : (self::$_helpers[$name] = self::getResource($name, 'helper'));
    }

    /**
     * get api object
     * @param string $name, etc: core@testcollate
     * @return api
     */
    static public function getApi($name)
    {
        return isset(self::$_apis[$name]) ? self::$_apis[$name] : (self::$_apis[$name] = self::getResource($name, 'api'));
    }

    /**
     * get table object
     * @param string $name
     * @return Model_Table
     */
    static public function getTable($name)
    {
        return isset(self::$_tables[$name]) ? self::$_tables[$name] : (self::$_tables[$name] = self::getResource($name, 'table'));
    }

    /**
     * @param string $name
     * @param string $spec
     * @return void
     */
    static public function setKind($name, $spec)
    {
        self::$_kinds[$name] = $spec;
    }

    /**
     * @param array $listeners
     * @return void
     */
    static public function setListeners($listeners)
    {
        self::$_listeners = $listeners;
    }

    /**
     * add hook
     * @param string $name
     * @param array $spec
     * @return void
     */
    static public function registerListener($name, $spec)
    {
        self::$_listeners[$name][] = $spec;
    }

    /**
     * has events
     * @param string $event
     * @return bool
     */
    static public function hasListener($event)
    {
        return isset(self::$_listeners[$name]);
    }

    /**
     * @var string $event
     * @param object $caller
     * @param array $params
     * @return void
     */
    static public function fireEvent($event, $caller = null, $params = null, $extra1 = null, $extra2 = null, $extra3 = null, $extra4 = null)
    {
        if (isset(self::$_listeners[$event]))
        {
            foreach (self::$_listeners[$event] as $callable)
            {
                call_user_func_array($callable, array(
                    $caller,
                    $params,
                    $extra1,
                    $extra2,
                    $extra3,
                    $extra4
                ));
            }
        }
    }

    /**
     * alias should be called before process load resource, it's better at the
     * time of bootstrap request.
     * @param string $name
     * @param string $type
     * @return object
     * @throws Exception
     */
    static public function getResource($name, $type)
    {
        $list = array(
            'api' => '.api.',
            'table' => '.model.table.',
            'service' => '.service.',
            'controller' => '.controller.',
            'block' => '.controller.block.',
            'plugin' => '.plugin.',
            'helper' => '.plugin.helper.',
            'element' => '.html.element.',
            'form' => '.form.',
            'htmlRender' => '.html.render.'
        );

        /**
         * recover type
         */
        $type = isset($list[$type]) ? $list[$type] : '.' . $type . '.';

        /**
         * check class
         * @var string
         */
        $class = trim(implode('_', array_map('ucfirst', explode('.', str_replace('@', $type, $name)))), '_');

        /**
         * batch alias from another alias.
         */
        if (isset(self::$_alias[$class]))
        {
            $class = self::$_alias[$class];
        }

        if (class_exists($class, 1))
        {
            return new $class;
        }

        /**
         * throw exception and kill concurrence to stop futher error.
         */
        throw new Exception("resource $type $name does not exists!");
    }

    /**
     * @param string $class
     * @return bool
     */
    static public function loadClass($class)
    {
        return class_exists($class, false) or self::__autoload($class);
    }

    /**
     * @param string $class
     * @return bool
     */
    static public function __autoload($class)
    {
        if (file_exists($file = NUO_MODULE_PATH . '/' . strtolower(str_replace('_', '/', $class)) . '.php'))
        {
            require_once $file;

            if (class_exists($class, 0))
            {
                return 1;
            }
        }

        if (file_exists($file = NUO_CORE_PATH . '/' . strtolower(str_replace('_', '/', $class)) . '.php'))
        {
            require_once $file;

            if (class_exists($class, 0))
            {
                return 1;
            }
        }

        if (file_exists($file = NUO_LIBRARY_PATH . '/' . str_replace('_', '/', $class) . '.php'))
        {
            require_once $file;
            if (class_exists($class, 0))
            {
                return 1;
            }
        }

        return 0;
    }

    /**
     * init locale
     * @return string
     */
    static public function initLocale()
    {
        return 'en_US';
    }

    /**
     * init timzone
     * @return string
     */
    static public function initTimezone()
    {
        return 'UTC';
    }

    /**
     * init currency format
     * @return string
     */
    static public function initCurrency()
    {
        return 'USD';
    }

    static public function getForm($name)
    {
        return self::getResource($name, 'form');

    }

    static public function getElement($name)
    {
        return self::getResource($name, 'element');
    }

    /**
     * @var string $uri
     * @return full string url.
     */
    static public function url($uri, $params = null)
    {
        if ($params)
        {
            $uri .= '?' . http_build_query($params);
        }

        return self::$_baseHref . $uri;
    }

    static public function getViewer()
    {

        if (null == self::$_viewer)
        {

            require_once 'Zend/Auth.php';
            require_once 'user/model/table/user.php';

            /**
             * match session.
             */
            $auth = Zend_Auth::getInstance();
            $uid = $auth -> getIdentity();
            /**
             * batch user id.
             */

            $user = NULL;

            if ($uid)
            {
                $table = Nuo::getTable('user@user');
                $user = $table -> find($uid) -> current();
            }

            if (!is_object($user))
            {
                $user = new user_model_table_row_user();
            }

            self::$_viewer = $user;
        }

        return self::$_viewer;
    }

    /**
     * @return void
     */
    static public function setViewer($user)
    {
        self::$_viewer = $user;
    }

    /**
     * @return Request
     */
    static public function getRequest()
    {
        if (null == self::$_request)
        {
            if ('cli' == php_sapi_name())
            {
                require_once 'request/cli.php';
                self::$_request = new Request_Cli;
            }
            else
            {
                require_once 'request/http.php';
                self::$_request = new Request_Http;
            }
        }
        return self::$_request;
    }

    /**
     * @return Response
     */
    static public function getResponse()
    {
        if (null == self::$_response)
        {
            require_once 'response.php';
            self::$_response = new Response;
        }
        return self::$_response;

    }

    /**
     * init router.
     */
    public function getRouter()
    {
        if (null == self::$_router)
        {

            require_once 'router.php';

            $router = new Router();
            $router -> setRoutes(
            include NUO_SETTING_PATH . '/route.php') -> setRules(
            include NUO_SETTING_PATH . '/route-rule.php');
            self::$_router = $router;
        }
        return self::$_router;
    }

}

/**
 * @param string $string
 * @return string
 */
function inflect_string($string)
{
    return str_replace(' ', '', ucwords(str_replace(array(
        '.',
        '-'
    ), ' ', $string)));
}

/**
 * @param string $string
 * @return string
 */
function deflect_string($string)
{
    return strtolower(trim(preg_replace('/([a-z0-9])([A-Z])/', '\1-\2', $string), '-. '));
}
